#ifndef RP2SPATIAL_H
#define RP2SPATIAL_H

#include "Rp2Object.h"
#include "Rp2BoundingVolume.h"
#include "Rp2Effect.h"
#include "Rp2GlobalState.h"
#include "Rp2Transformation.h"

namespace Rp2
{

class Culler;
class Light;

class Spatial : public Object
{
RP2_DECLARE_RTTI;
RP2_DECLARE_NAME_ID;

public:
	virtual ~Spatial();
	
	// local and world transforms. If World transform is set
	// directly, WorldIsCurrent should set to true
	Transformation Local;
	Transformation World;
	bool WorldIsCurrent;

    // World bound access.  In some situations you might want to set the
    // world bound directly and bypass the Spatial::UpdateGS() mechanism.  If
    // WorldBound is set directly, the WorldBoundIsCurrent flag should be set
    // to true.
    BoundingVolumePtr WorldBound;
    bool WorldBoundIsCurrent;

    // Culling parameters.
    enum CullingMode
    {
        // Determine visibility state by comparing the world bounding volume
        // to culling planes.
        CULL_DYNAMIC,

        // Force the object to be culled.  If a Node is culled, its entire
        // subtree is culled.
        CULL_ALWAYS,

        // Never cull the object.  If a Node is never culled, its entire
        // subtree is never culled.  To accomplish this, the first time such
        // a Node is encountered, the bNoCull parameter is set to 'true' in
        // the recursive chain GetVisibleSet/OnGetVisibleSet.
        CULL_NEVER,

        MAX_CULLING_MODE
    };

    CullingMode Culling;


	// parent access
	Spatial* GetParent();

    // Update of geometric state and controllers.  The UpdateGS function
    // computes world transformations on the downward pass and world bounding
    // volumes on the upward pass.  The UpdateBS function just computes the
    // world bounding volumes on an upward pass.  This is useful if model
    // data changes, causing the model and world bounds to change, but no
    // transformations need recomputing.
	void UpdateGS(double dAppTime = -Mathd::MAX_REAL, 
		bool bInitiator = true);
	void UpdateBS();

	// Update render states
	void UpdateRS(std::vector<GlobalState*>* akGStack = 0,
		std::vector<Light*>* pkLStack = 0);

	// global state
	int GetGlobalStateQuantity() const;
	GlobalState* GetGlobalState (int i) const;
	GlobalState* GetGlobalState (GlobalState::StateType eType) const;
	void AttachGlobalState (GlobalState* pkState);
	void DetachGlobalState (GlobalState::StateType eType);
	void DetachAllGlobalStates();

	// light state
    int GetLightQuantity () const;
    Light* GetLight (int i) const;
    void AttachLight (Light* pkLight);
    void DetachLight (Light* pkLight);
    void DetachAllLights ();

	// Shader Effects
	int GetEffectQuantity() const;
	Effect* GetEffect(int i) const;
	void AttachEffect(Effect* pkEffect);
    void DetachEffect (Effect* pkEffect);
    void DetachAllEffects ();
    void SetStartEffect (int i);
    int GetStartEffect () const;

protected:
	Spatial();

	// Geometry updates
	virtual void UpdateWorldData(double dAppTime = -Mathd::MAX_REAL);
	virtual void UpdateWorldBound () = 0;
	void PropagateBoundToRoot();

	// support for hierarchical scene graph
	Spatial* m_pkParent;

	// light state
	std::vector<ObjectPtr> m_kLights;

    // render state updates
    void PropagateStateFromRoot (std::vector<GlobalState*>* akGStack,
        std::vector<Light*>* pkLStack);
    void PushState (std::vector<GlobalState*>* akGStack,
        std::vector<Light*>* pkLStack);
    void PopState (std::vector<GlobalState*>* akGStack,
        std::vector<Light*>* pkLStack);
    virtual void UpdateState (std::vector<GlobalState*>* akGStack,
        std::vector<Light*>* pkLStack) = 0;

	// global render states
	std::vector<GlobalStatePtr> m_kGlobalStates;

	// shader effects
	mutable std::vector<EffectPtr> m_kEffects;

    // Normally, all effects are applied to an object.  To allow overriding
    // some of the effects, a starting index may be specified by the
    // application.  This is useful for complex effects, where the current
    // effects must be ignored and another effect is used instead.  Without
    // this mechanism, you would have to detach and save the current effects,
    // attach the desired effect, draw, detach the desired effect, and
    // reattach the old effects.  With this mechanism, you attach the desired
    // effect, set the starting index to that of the desired effect, draw,
    // reset the starting index to zero, and detach the desired effect.
    int m_iStartEffect;


// internal use
public:
	void SetParent(Spatial* pkSpatial);

    // culling
    void OnGetVisibleSet (Culler& rkCuller, bool bNoCull);
    virtual void GetVisibleSet (Culler& rkCuller, bool bNoCull) = 0;
};

typedef Pointer<Spatial> SpatialPtr;

#include "Rp2Spatial.inl"

}

#endif